#include <rtthread.h>
#include <rtdevice.h>

#include <dfs.h>
#include <dfs_fs.h>
#include <dfs_file.h>
#include <dfs_poll.h>

#include <stdarg.h>
#include <mqueue.h>

#include "mqfs.h"

static int vfs_mqfs_open(struct dfs_fd *file, ...)
{
    va_list ap;
    struct mq_attr *attr;
    mode_t mode;
    struct mqfs_dirent *root, *item;
    int ret;

    va_start(ap, file);
    mode = va_arg(ap, mode_t);
    attr = va_arg(ap, struct mq_attr *);
    va_end(ap);

    root = (struct mqfs_dirent*)file->fs->data;
    ret = mqfs_open(root, file->path, file->flags, 
                           mode, attr, &item);

    if (ret == 0)
    {
        file->data = item;
    }

    return ret;
}

static int vfs_mqfs_close(struct dfs_fd *file)
{
    return 0;
}

static int vfs_mqfs_read(struct dfs_fd *file, void *buf, size_t count, ...)
{
    va_list ap;
    struct mqfs_dirent *item;
    int len;
    unsigned *prio;

    va_start(ap, count);
    prio = va_arg(ap, unsigned*);
    va_end(ap);

    item = (struct mqfs_dirent*)file->data;

    do
    {
        len = mqfs_read(item, buf, count, prio);
        if (len == 0)
        {
            if (file->flags & O_NONBLOCK)
            {
                len = -EAGAIN;
            }
            else
            {
                rt_wqueue_wait(&item->rwq, 0, -1);
            }
        }
        else if (len > 0)
        {
            rt_wqueue_wakeup(&item->wwq, (void*)POLLOUT);
        }

        if (item->isdel)
        {
            len = -EPIPE;
        }
    } while (!len);

    return len;
}

static int vfs_mqfs_write(struct dfs_fd *file, const void *buf, size_t count, ...)
{
    va_list ap;
    struct mqfs_dirent *item;
    int len;
    unsigned prio;

    va_start(ap, count);
    prio = va_arg(ap, unsigned);
    va_end(ap);

    item = (struct mqfs_dirent*)file->data;

    do
    {
        len = mqfs_write(item, buf, count, prio);
        if (len == 0)
        {
            if (file->flags & O_NONBLOCK)
            {
                len = -EAGAIN;
            }
            else
            {
                rt_wqueue_wait(&item->wwq, 0, -1);
            }
        }
        else if (len > 0)
        {
            rt_wqueue_wakeup(&item->rwq, (void*)POLLIN);
        }

        if (item->isdel)
        {
            len = -EPIPE;
        }
    } while (!len);

    return len;
}

static int vfs_mqfs_poll(struct dfs_fd *fd, rt_pollreq_t *req)
{
    int mask = 0;
    struct mqfs_dirent *mq;

    mq = (struct mqfs_dirent *)fd->data;

    if (mq->curmsgs != 0)
        mask |= POLLIN;
    else
        rt_poll_add(&mq->rwq, req);

    if (mq->curmsgs < mq->maxmsgs)
        mask |= POLLOUT;
    else
        rt_poll_add(&mq->rwq, req);

    return mask;
}

static int vfs_mqfs_mount(struct dfs_filesystem* fs, unsigned long flag, const void* data)
{
    (void)data;
    struct mq_attr attr;

    attr.mq_maxmsg = 64;
    attr.mq_msgsize = 0;
    fs->data = mqfs_dirent_alloc("/", 0, &attr);
    if (!fs->data)
        return -ENOMEM;

    return 0;
}

static const struct dfs_file_ops _mq_fops = 
{
    vfs_mqfs_open,
    vfs_mqfs_close,
    RT_NULL,
    vfs_mqfs_read,
    vfs_mqfs_write,
    RT_NULL,                    /* flush */
    RT_NULL,                    /* lseek */
    RT_NULL,
    vfs_mqfs_poll,
};

static const struct dfs_filesystem_ops _mq_fs = 
{
    "mqueue",
    DFS_FS_FLAG_DEFAULT,
    &_mq_fops,

    vfs_mqfs_mount,
    RT_NULL,
    RT_NULL,
    RT_NULL,

    RT_NULL,
    RT_NULL,
    RT_NULL,
};

int mqfs_init(void)
{
    int ret;
    const struct dfs_filesystem_ops *fs = &_mq_fs;

    ret = dfs_register(fs);
    if (ret == 0)
    {
        ret = dfs_pseudo_mount(":mq", fs, 0);
    }

    return ret;
}
INIT_COMPONENT_EXPORT(mqfs_init);
